﻿using System;
using System.Collections.Generic;

namespace Core
{
    public class EventSystem : Component
    {
        private readonly IReadOnlyList<Component> empty = new List<Component>();

        private readonly Dictionary<string, List<Type>> allTypes = new();
        private readonly Dictionary<string, List<Component>> allEvents = new();

        public void Awake()
        {
            log.d("\tEventSystem.Awake");
            Init();
        }

        public override void Dispose()
        {
            if (IsDisposed) return;
            base.Dispose();

            log.d("\tEventSystem.Dispose");
        }

        private void Init()
        {
            var types = S.Assembly.GetAttrTypes(typeof(EventAttribute));
            foreach (var type in types)
            {
                if (type.FullName == null) continue;
                if (type.FullName.StartsWith(G.Name) == false)
                {
                    continue;
                }

                if (!type.IsDefined(typeof(EventAttribute), false))
                {
                    continue;
                }

                object[] attrs = type.GetCustomAttributes(typeof(EventAttribute), false);

                if (attrs.Length == 0 || attrs[0] is not EventAttribute attr)
                {
                    continue;
                }

                if (!allTypes.ContainsKey(attr.Name))
                {
                    allTypes[attr.Name] = new List<Type>();
                }

                var lst = allTypes[attr.Name];
                lst.Add(type);
            }
        }

        private void CreateEvents(string event_name)
        {
            if (allTypes.ContainsKey(event_name) == false)
            {
                return;
            }

            if (allEvents.ContainsKey(event_name) == false)
            {
                allEvents[event_name] = new List<Component>();
            }

            var typeList = allTypes[event_name];
            var eventList = allEvents[event_name];

            foreach (var type in typeList)
            {
                var obj = G.Factory.Create(type);
                if (obj == null)
                {
                    log.err($"Error! Event obj is null, name={event_name} game={G.Name}");
                    continue;
                }

                eventList.Add(obj);
            }
        }

        private IReadOnlyList<Component> GetEvents(string event_name)
        {
            if (!allEvents.ContainsKey(event_name))
            {
                CreateEvents(event_name);
                if (!allEvents.ContainsKey(event_name))
                {
                    return empty;
                }
            }

            return allEvents[event_name];
        }

        public void Register(string event_name, Component handler)
        {
            if (string.IsNullOrEmpty(event_name) || handler == null)
            {
                return;
            }

            if (allEvents.ContainsKey(event_name) == false)
            {
                allEvents[event_name] = new List<Component>();
            }

            var list = allEvents[event_name];
            if (list.Contains(handler))
            {
                return;
            }

            list.Add(handler);
        }

        public void UnRegister(string event_name, Component handler)
        {
            if (string.IsNullOrEmpty(event_name) || handler == null)
            {
                return;
            }

            if (!allEvents.TryGetValue(event_name, out var list))
            {
                return;
            }

            list.Remove(handler);
        }

        public void Run(string event_name)
        {
            var lst = GetEvents(event_name);
            foreach (var item in lst)
            {
                if (item is IEventHandler handler)
                {
                    try
                    {
                        (handler as IPrepare)?.Prepare();
                        handler.Run();
                    }
                    catch (Exception ex)
                    {
                        log.err(ex);
                    }
                }
            }
        }

        public void Run<A>(string event_name, A a)
        {
            var lst = GetEvents(event_name);
            foreach (var item in lst)
            {
                if (item is IEventHandler<A> handler)
                {
                    try
                    {
                        (handler as IPrepare)?.Prepare();
                        handler.Run(a);
                    }
                    catch (Exception ex)
                    {
                        log.err(ex);
                    }
                }
            }
        }

        public void Run<A, B>(string event_name, A a, B b)
        {
            var lst = GetEvents(event_name);
            foreach (var item in lst)
            {
                if (item is IEventHandler<A, B> handler)
                {
                    try
                    {
                        (handler as IPrepare)?.Prepare();
                        handler.Run(a, b);
                    }
                    catch (Exception ex)
                    {
                        log.err(ex);
                    }
                }
            }
        }

        public void Run<A, B, C>(string event_name, A a, B b, C c)
        {
            var lst = GetEvents(event_name);
            foreach (var item in lst)
            {
                if (item is IEventHandler<A, B, C> handler)
                {
                    try
                    {
                        (handler as IPrepare)?.Prepare();
                        handler.Run(a, b, c);
                    }
                    catch (Exception ex)
                    {
                        log.err(ex);
                    }
                }
            }
        }


        public void Run<A, B, C, D>(string event_name, A a, B b, C c, D d)
        {
            var lst = GetEvents(event_name);
            foreach (var item in lst)
            {
                if (item is IEventHandler<A, B, C, D> handler)
                {
                    try
                    {
                        (handler as IPrepare)?.Prepare();
                        handler.Run(a, b, c, d);
                    }
                    catch (Exception ex)
                    {
                        log.err(ex);
                    }
                }
            }
        }
    }
}
